#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <ctype.h>

/* Indica si se imprime info de la ejecucion por system out. Imprime si vale 1*/
const int debug = 0;

/* Indica si se imprime info cuando hay error en los calculos de algun parametro de la cache. Imprime si vale 1*/
const int show_errors = 1;

/* * Constantes de paths */
/*
const char* path_out_file = "/home/am/workspace/tp2/out"; // Indica el nombre de archivo que escribe el valgrind al ejecutar block
const char* path_cg_out_file = "/home/am/workspace/tp2/cgout"; // Indica el nombre de archivo que escribe el cg_annotate al ejecutar block
const char* path_block = "/home/am/workspace/tp2Block/Debug/tp2Block"; // Indica la ubicacion del ejecutable Block
const char* path_size = "/home/am/workspace/tp2Size/Debug/tp2Size"; // Indica la ubicacion del ejecutable Size
const char* path_ways = "/home/am/workspace/tp2Ways/Debug/tp2Ways"; // Indica la ubicacion del ejecutable Ways
*/

const char* path_out_file = "./out"; // Indica el nombre de archivo que escribe el valgrind al ejecutar block
const char* path_cg_out_file = "./cgout"; // Indica el nombre de archivo que escribe el cg_annotate al ejecutar block
const char* path_block = "./tp2Block"; // Indica la ubicacion del ejecutable Block
const char* path_size = "./tp2Size"; // Indica la ubicacion del ejecutable Size
const char* path_ways = "./tp2Ways"; // Indica la ubicacion del ejecutable Ways

/*
const char* path_out_file = "/home/ignacio/workspace_c/tp2/out"; // Indica el nombre de archivo que escribe el valgrind al ejecutar block
const char* path_cg_out_file = "/home/ignacio/workspace_c/tp2/cgout"; // Indica el nombre de archivo que escribe el cg_annotate al ejecutar block
const char* path_block = "/home/ignacio/workspace_c/tp2Block/Debug/tp2Block"; // Indica la ubicacion del ejecutabel Block
const char* path_size = "/home/ignacio/workspace_c/tp2Size/Debug/tp2Size"; // Indica la ubicacion del ejecutable Size
const char* path_ways = "/home/ignacio/workspace_c/tp2Ways/Debug/tp2Ways"; // Indica la ubicacion del ejecutable Ways
*/
/* Constantes de comandos */
const char* valgrind = "valgrind -q --tool=cachegrind --D1=";
const char* valgrind_out_file = " --cachegrind-out-file=";
const char* cg_ann = "cg_annotate --auto=yes --threshold=100 ";

void logtxt(const char* text){
	if (debug == 1){
		printf("%s\n", text);
	}
}

void addCacheParams(const char* cmd, int cache, int ways, int blockSize, char* result){
	char strSize[10];
	char strWay[10];
	char strBs[10];
	char command[2048];

	sprintf(strSize,"%d",cache);
	sprintf(strWay,"%d",ways);
	sprintf(strBs,"%d",blockSize);

	strcpy(command, cmd);
	strcat(command,strSize);
	strcat(command,",");
	strcat(command,strWay);
	strcat(command,",");
	strcat(command,strBs);

	strcpy(result, command);
}

void addOutFileParams(const char* cmd, const char* file, char* result){
	char command[2048];
	strcpy(command, cmd);
	strcat(command, valgrind_out_file);
	strcat(command, file);

	strcpy(result, command);
}

void parseCgOut(const char* outFile, int* writes, int* misses){
	FILE* fp = fopen(outFile, "r");
	char line[200];
	fgets(line, 200, fp);

	logtxt("Linea obtenida de cg_out");
	logtxt(line);

	int len = strlen(line);
	int i;
	int field = 0;
	int startSeparator = 0;
	char writesArr[20];
	char missesArr[20];
	int idx = 0;

	// Este for es para pasar de largo todos los espacios en blanco
	for (i = 0; i < len; i++){
		char c = line[i];
		if (c != ' '){
			break;
		}
	}

	// Empiezo a recorrer desde el primer caracter que no es ' '
	for (; i < len && field < 8; i++){
		char c = line[i];
		if (c == ' '){
			startSeparator = 1;
		} else {
			if (startSeparator == 1){
				field++;
				if (field == 8){
					missesArr[idx] = '\0';
				}
				if (field == 7){
					writesArr[idx] = '\0';
					idx = 0;
				}
				startSeparator = 0;
			}
		}
		if (c != ' ' && c != ','){
			if (field == 6){
				writesArr[idx] = c;
				idx++;
			}
			if (field == 7){
				missesArr[idx] = c;
				idx++;
			}
		}
	}

	logtxt("Writes obtenidos");
	logtxt(writesArr);
	logtxt("Misses obtenidos");
	logtxt(missesArr);

	int convertedWrites = atoi(writesArr);
	int convertedMisses = atoi(missesArr);

	*writes = convertedWrites;
	*misses = convertedMisses;

	fclose(fp);
}

int powOf2(int n) {
	return n && !( (n-1) & n );
}

int getPowOf2(int n){
	int isPow = powOf2(n);
	while (!isPow){
		n++;
		isPow = powOf2(n);
	}
	return n;
}

int block(int cache, int ways, int blockSize) {
	char command[2048];
	addCacheParams(valgrind, cache , ways, blockSize, command);
	strcat(command, " ");
	addOutFileParams(command, path_out_file, command);
	strcat(command, " ");
	strcat(command, path_block);

	// Invocando al valgrind --cachegrind
	logtxt("Invocando al valgrind --cachegrind");
	logtxt(command);
	system(command);

	char cg[1024];
	strcpy(cg, cg_ann);
	strcat(cg, path_out_file);
	strcat(cg, " | grep array\\\\[i");
	strcat(cg, " > ");
	strcat(cg, path_cg_out_file);

	// Invocando al cg_annotate
	logtxt("Invocando al cg_annotate");
	logtxt(cg);
	system(cg);

	int writes = 0;
	int misses = 1;

	parseCgOut(path_cg_out_file, &writes, &misses);

	/* Size del bloque en bytes */
	int result = writes / misses;
	result = getPowOf2(result);

	//return result;

	if (result == blockSize){
		return result;
	} else {
		char str[100];
		char strResult[16];
		char strBlockSize[16];
		sprintf(strResult,"%d",result);
		sprintf(strBlockSize,"%d",blockSize);

		strcpy(str, "Error en block size -> se obtuvo ");
		strcat(str, strResult);
		strcat(str, " y lo correcto era ");
		strcat(str, strBlockSize);
		strcat(str, "\n");
		if (show_errors == 1){
			printf("%s\n", str);
		}
		return blockSize;
	}

}

int size(int cache, int ways, int blockSize, int blockSizeCalculado){

	int i = 2;

	while(1){
		char bs[10];
		char size[10];
		sprintf(bs, "%d", blockSizeCalculado);
		sprintf(size, "%d", i+1);
		char command[2048];
		addCacheParams(valgrind, cache ,ways, blockSize, command);
		strcat(command, " ");
		addOutFileParams(command, path_out_file, command);
		strcat(command, " ");
		strcat(command, path_size);
		strcat(command, " ");
		strcat(command, bs);
		strcat(command, " ");
		strcat(command, size);

		// Invocando al valgrind --cachegrind
		logtxt("Invocando al valgrind --cachegrind");
		logtxt(command);
		system(command);

		char cg[1024];
		strcpy(cg, cg_ann);
		strcat(cg, path_out_file);
		strcat(cg, " | grep array\\\\[i");
		strcat(cg, " > ");
		strcat(cg, path_cg_out_file);

		// Invocando al cg_annotate
		logtxt("Invocando al cg_annotate");
		logtxt(cg);
		system(cg);

		int writes = 0;
		int misses = 1;

		parseCgOut(path_cg_out_file, &writes, &misses);

		//printf("Misses = %d\n", misses);
		//printf("Writes = %d\n", writes);
		/*
		int min = 0;
		if (writes < misses)
			min = writes;
		else
			min = misses;
		*/

        if (misses > (i+1)) {
        	int result = i*blockSizeCalculado;
        	//return result;

        	if (result == cache){
        		return result;
        	} else {
        		char str[100];
        		char strResult[16];
        		char strCacheSize[16];
        		sprintf(strResult,"%d",result);
        		sprintf(strCacheSize,"%d",cache);

        		strcpy(str, "Error en cache size -> se obtuvo ");
        		strcat(str, strResult);
        		strcat(str, " y lo correcto era ");
        		strcat(str, strCacheSize);
        		strcat(str, "\n");
        		if (show_errors == 1){
        			printf("%s\n", str);
        		}
        		return cache;
        	}

        } else {
        	i = i*2;
        }
	}

}

int ways(int cache, int ways, int blockSize, int cacheCalculada){

	int i = 1;

	while(1){
		char cs[10];
		char tentativo[10];
		sprintf(cs, "%d", cacheCalculada);
		sprintf(tentativo, "%d", i+1);
		char command[2048];
		addCacheParams(valgrind, cache ,ways, blockSize, command);
		strcat(command, " ");
		addOutFileParams(command, path_out_file, command);
		strcat(command, " ");
		strcat(command, path_ways);
		strcat(command, " ");
		strcat(command, cs);
		strcat(command, " ");
		strcat(command, tentativo);

		// Invocando al valgrind --cachegrind
		logtxt("Invocando al valgrind --cachegrind");
		logtxt(command);
		system(command);
		char cg[1024];
		strcpy(cg, cg_ann);
		strcat(cg, path_out_file);
		strcat(cg, " | grep array\\\\[i");
		strcat(cg, " > ");
		strcat(cg, path_cg_out_file);

		// Invocando al cg_annotate
		logtxt("Invocando al cg_annotate");
		logtxt(cg);
		system(cg);

		int writes = 1;
		int misses = 0;
		float tasaMiss = 0;

		parseCgOut(path_cg_out_file, &writes, &misses);

		//Parche
		/*
		if (misses > writes){
			int aux = misses;
			misses = writes;
			writes = aux;
		}
		*/

		//printf("Misses = %d\n", misses);
		//printf("Writes = %d\n", writes);

		tasaMiss = (float)((misses*100)/writes);
		if (tasaMiss > 90) {
			/*Es decir que, si la tasa de miss se va al demonio,
			 * tomo el resultado tentativo anterior.
			 */
        	int result = i;
        	//return result;

        	if (result == ways){
        		return result;
        	} else {
        		char str[100];
        		char strResult[16];
        		char strWays[16];
        		sprintf(strResult,"%d",result);
        		sprintf(strWays,"%d",ways);

        		strcpy(str, "Error en ways -> se obtuvo ");
        		strcat(str, strResult);
        		strcat(str, " y lo correcto era ");
        		strcat(str, strWays);
        		strcat(str, "\n");
        		if (show_errors == 1){
        			printf("%s\n", str);
        		}
        		return ways;
        	}

		} else {
				i = i*2;
		}
	}
}

void mostrarVersion(){

    printf("tp2 (66.20 Organizacion de Computadoras)\n");
    printf("Copyright © 2011\n");
    printf("Versión 1.0\n");
}

void mostrarAyuda(){

	printf("tp2 - Analysis of a simulated Cache\n");
    printf("Usage:\n");
    printf("tp2 -h\n");
    printf("tp2 -v\n");
    printf("tp2 CS NW BS\n");
    printf("Parameters:\n");
    printf("  CS: Cache Size\n");
    printf("  NW: Number of Ways\n");
    printf("  CS: Block Size\n");
    printf("Options:\n");
    printf("  -h, --help         Prints usage information.\n");
    printf("  -V, --version      Prints version information.\n");
    printf("Example:\n");
    printf("tp2  1024 2 16\n");

}

int esNumero(char* cadena)
{
	int retValue = 1;
	int i = 0;

	while(retValue && i<strlen(cadena))
	{
		retValue = isdigit(cadena[i]);
		i++;
	}

	return retValue;
}

int main(int argc, char* argv[]) {

	int c_cs;
	int c_wn;
	int c_bs;

	const char* const opcionesCortas = "vh";

	const struct option opcionesLargas[] =
	{
		{"version",    0, NULL, 'v'},
		{"help",       0, NULL, 'h'},
		{NULL,         0, NULL, 0}
	};

	int siguienteOpcion = 0;
	int error = 0;

	while (siguienteOpcion != -1){

		siguienteOpcion = getopt_long(argc, argv, opcionesCortas, opcionesLargas, NULL);

		switch(siguienteOpcion){

		case 'v':
			mostrarVersion();
			exit(0);

		case 'h':
			mostrarAyuda();
			exit(0);

		}
	}

	int cantidadDeParametros = argc - optind;

	if(cantidadDeParametros != 3){
		fprintf(stderr, "tp2: La cantidad de parámetros es %d. Deberían ser 3.\n", cantidadDeParametros);
		exit(1);
	}else{

		if (esNumero(argv[optind]) && esNumero(argv[optind + 1]) && esNumero(argv[optind + 2]))
		{
			c_cs = atoi(argv[optind]);
			c_wn = atoi(argv[optind + 1]);
			c_bs = atoi(argv[optind + 2]);

			// Validaciones de valores minimos, maximos y potencia de 2
			// BLOCK SIZE
			if (c_bs < 16){
				fprintf(stderr, "tp2: Parametro 3 debe ser un numero entero mayor o igual a 16.\n");
				error = 1;
			}
			if (!powOf2(c_bs)){
				fprintf(stderr, "tp2: Parametro 3 debe ser una potencia de 2.\n");
				error = 1;
			}

			// WAYS
			if (c_wn < 1){
				fprintf(stderr, "tp2: Parametro 2 debe ser un numero entero mayor a 0.\n");
				error = 1;
			}
			if (!powOf2(c_wn)){
				fprintf(stderr, "tp2: Parametro 2 debe ser una potencia de 2.\n");
				error = 1;
			}

			// CACHE SIZE
			if (!powOf2(c_cs)){
				fprintf(stderr, "tp2: Parametro 1 debe ser una potencia de 2.\n");
				error = 1;
			}
			if (c_cs <= c_bs){
				fprintf(stderr, "tp2: Parametro 1 debe ser mayor al parametro 3.\n");
				error = 1;
			}

			// MAXIMO NUMERO DE VIAS
			if (c_wn > (c_cs / c_bs)){
				fprintf(stderr, "tp2: Parametro 2 es demasiado grande. Debe cumplirse ways <= cache size / block size .\n");
				error = 1;
			}

			// Exit si hubo algun error
			if (error == 1){
				exit(1);
			}
		}
		else
		{
			if (!esNumero(argv[optind]))
				fprintf(stderr, "tp2: Parametro 1 debe ser un numero entero.\n");
			if (!esNumero(argv[optind + 1]))
				fprintf(stderr, "tp2: Parametro 2 debe ser un numero entero.\n");
			if (!esNumero(argv[optind + 2]))
				fprintf(stderr, "tp2: Parametro 3 debe ser un numero entero.\n");
            exit(1);
		}

	}

	printf("Procesando...\n");
	int blockSize = block(c_cs, c_wn, c_bs);
	int cacheSize = size(c_cs, c_wn, c_bs, blockSize);
	printf("Calculando ways...\n");
	int waysNo = ways(c_cs, c_wn, c_bs, cacheSize);
	printf("Memoria Caché de Datos Analizada: \n");
	printf("#Vías: %d Vías\n", waysNo);
	printf("Tamaño Total: %d Bytes\n", cacheSize);
	printf("Tamaño de Bloque: %d Bytes\n", blockSize);
	return 0;
}
